package com.safe.app.config.init.redis;


import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.safe.app.utils.constants.SystemConstant;
import com.safe.app.utils.exceptions.base.ErrorMsgException;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;

import java.util.List;
import java.util.Map;
import java.util.Set;

@Slf4j
public class RedisServer implements CacheInterface {

    private static final String LOG_ERROR_REDIS_CACHE = "【========= Redis 缓存异常 =========】";

    @Autowired
    private JedisConnectionFactory jedisConnectionFactory;

    private String redisKey = "KEY:";

    public RedisServer(String redisKey) {
        super();
        this.redisKey = redisKey;
    }

    @Override
    public void set(String key, Object value) {
        set(key, value, 0);
    }

    /**
     * @author h2l
     * @param expirationTime ： 缓存过期时间（单位秒）
     */
    @Override
    public void set(String key, Object value, int expirationTime) {
        set(key, value, 0, expirationTime);
    }

    /**
     * 获取 一个 redis连接
     * <p>
     * 必须在finally 调用{@link RedisConnection#close()} 释放连接
     *
     * @return
     */
    public RedisConnection getRedisConnection() {
        return jedisConnectionFactory.getConnection();
    }

    @Override
    public void set(String key, Object value, int timeToIdleSeconds, int expirationTime) {
        RedisConnection conn = null;
        try {
            if (StringUtils.isBlank(key)) {
                throw new ErrorMsgException("Properties \"KEY\" Not NULL ");
            }
            conn = jedisConnectionFactory.getConnection();
            if (!conn.isClosed() && value != null) {
                byte[] keyBytes = (redisKey + key).getBytes();
                byte[] valueBytes = SystemConstant.OBJECT_MAPPER.writeValueAsString(value).getBytes();
                if (expirationTime > 0) {
                    conn.setEx(keyBytes, expirationTime, valueBytes);
                } else {
                    conn.set(keyBytes, valueBytes);
                }
            }
        } catch (Exception e) {
            log.error(LOG_ERROR_REDIS_CACHE, e);
        } finally {
            if (conn != null) {
                conn.close();
            }
        }
    }

    @Override
    public void remove(String key) {
        RedisConnection conn = null;
        try {
            conn = jedisConnectionFactory.getConnection();
            conn.del((redisKey + key).getBytes());
        } catch (Exception e) {
            log.error(LOG_ERROR_REDIS_CACHE, e);
        } finally {
            if (conn != null) {
                conn.close();
            }
        }
    }

    @Override
    public Object get(String key) {
        try {
            return get(key, Object.class);
        } catch (Exception e) {
            remove(key);
            log.error(LOG_ERROR_REDIS_CACHE, e);
        }
        return null;
    }

    @Override
    public void removeAll() {
        RedisConnection conn = null;
        try {
            conn = jedisConnectionFactory.getConnection();
            conn.flushDb();
        } catch (Exception e) {
            log.error(LOG_ERROR_REDIS_CACHE, e);
        } finally {
            if (conn != null) {
                conn.close();
            }
        }
    }

    @Override
    public <T> T get(String key, Class<T> clazz) {
        RedisConnection conn = null;
        T value = null;
        try {
            conn = jedisConnectionFactory.getConnection();
            if (StringUtils.isNotBlank(key) && !conn.isClosed()) {
                byte[] in = conn.get((redisKey + key).getBytes());
                if (in != null && in.length > 0) {
                    value = SystemConstant.OBJECT_MAPPER.readValue(in, clazz);
                }
            }
        } catch (Exception e) {
            remove(key);
            log.error(LOG_ERROR_REDIS_CACHE, e);
        } finally {
            if (conn != null) {
                conn.close();
            }
        }
        return value;
    }

    @Override
    public <T> List<T> getList(String key, Class<T> clazz) {
        RedisConnection conn = null;
        List<T> value = null;
        try {
            conn = jedisConnectionFactory.getConnection();
            if (StringUtils.isNotBlank(key) && !conn.isClosed()) {
                byte[] in = conn.get((redisKey + key).getBytes());
                if (in != null && in.length > 0) {
                    value = SystemConstant.OBJECT_MAPPER.readValue(in, getCollectionType(SystemConstant.OBJECT_MAPPER, List.class, clazz));
                }
            }
        } catch (Exception e) {
            remove(key);
            log.error(LOG_ERROR_REDIS_CACHE, e);
        } finally {
            if (conn != null) {
                conn.close();
            }
        }
        return value;
    }

    @Override
    public <K, T> Map<K, T> getMap(String key, Class<K> keyClazz, Class<T> valClazz) {
        RedisConnection conn = null;
        Map<K, T> value = null;
        try {
            conn = jedisConnectionFactory.getConnection();
            if (StringUtils.isNotBlank(key) && !conn.isClosed()) {
                byte[] in = conn.get((redisKey + key).getBytes());
                if (in != null && in.length > 0) {
                    value = SystemConstant.OBJECT_MAPPER.readValue(in, getCollectionType(SystemConstant.OBJECT_MAPPER, Map.class, keyClazz, valClazz));
                }
            }
        } catch (Exception e) {
            remove(key);
            log.error(LOG_ERROR_REDIS_CACHE, e);
        } finally {
            if (conn != null) {
                conn.close();
            }
        }
        return value;
    }

    /**
     * 获取 指定 key 模糊匹配集合
     *
     * @param key
     * @return
     */
    public Set<byte[]> getKeys(String key) {
        RedisConnection conn = null;
        try {
            conn = jedisConnectionFactory.getConnection();
            return conn.keys((redisKey + key).getBytes());
        } catch (Exception e) {
            log.error(LOG_ERROR_REDIS_CACHE, e);
        } finally {
            if (conn != null) {
                conn.close();
            }
        }
        return null;
    }

    /**
     * 添加 set 集合数据
     *
     * @param key
     * @param values
     * @return
     * @author h2l
     */
    public boolean sadd(String key, byte[]... values) {
        return sadd(key, -1, values);
    }

    /**
     * 添加 set 集合数据
     *
     * @param key
     * @param values
     * @return
     * @author h2l
     */
    public boolean sadd(String key, int expirationTime, byte[]... values) {
        RedisConnection conn = null;
        try {
            conn = jedisConnectionFactory.getConnection();
            byte[] bytes = (redisKey + key).getBytes();
            if (conn.sAdd(bytes, values) == values.length) {
                if (expirationTime > 0) {
                    conn.expire(bytes, expirationTime);
                }
                return true;
            }
            return false;
        } catch (Exception e) {
            log.error(LOG_ERROR_REDIS_CACHE, e);
        } finally {
            if (conn != null) {
                conn.close();
            }
        }
        return false;
    }

    /**
     * 获取 set 集合缓存数据
     *
     * @param key
     * @return
     * @author h2l
     */
    public Set<byte[]> sMembers(String key) {
        RedisConnection conn = null;
        try {
            conn = jedisConnectionFactory.getConnection();
            return conn.sMembers((redisKey + key).getBytes());
        } catch (Exception e) {
            log.error(LOG_ERROR_REDIS_CACHE, e);
        } finally {
            if (conn != null) {
                conn.close();
            }
        }
        return null;
    }

    /**
     * 随机 获取 set 集合 内 指定大小 数据
     *
     * @param key   缓存 key
     * @param count 正数，且小于集合基数，那么命令返回一个包含 count 个元素的数组，数组中的元素各不相同。如果
     *              大于等于集合基数，那么返回整个集合---------------------
     *              负数，那么命令返回一个数组，数组中的元素可能会重复出现多次，而数组的长度为 count 的绝对值。
     * @return
     * @author h2l
     */
    public List<byte[]> sRandMember(String key, int count) {
        RedisConnection conn = null;
        try {
            conn = jedisConnectionFactory.getConnection();
            return conn.sRandMember((redisKey + key).getBytes(), count);
        } catch (Exception e) {
            log.error(LOG_ERROR_REDIS_CACHE, e);
        } finally {
            if (conn != null) {
                conn.close();
            }
        }
        return null;
    }

    /**
     * 删除 set 集合里面的key
     *
     * @param key
     * @param values
     * @return
     * @author h2l
     */
    public Long sRem(String key, byte[]... values) {
        RedisConnection conn = null;
        try {
            conn = jedisConnectionFactory.getConnection();
            return conn.sRem((redisKey + key).getBytes(), values);
        } catch (Exception e) {
            log.error(LOG_ERROR_REDIS_CACHE, e);
        } finally {
            if (conn != null) {
                conn.close();
            }
        }
        return null;
    }

    /**
     * 获取 set 内元素总个数
     *
     * @param key
     * @return
     * @author h2l
     */
    public Long sCard(String key) {
        RedisConnection conn = null;
        try {
            conn = jedisConnectionFactory.getConnection();
            return conn.sCard((redisKey + key).getBytes());
        } catch (Exception e) {
            log.error(LOG_ERROR_REDIS_CACHE, e);
        } finally {
            if (conn != null) {
                conn.close();
            }
        }
        return null;
    }

    /**
     * 判断 value 元素是否 是集合 key 的成员
     *
     * @param key
     * @param value
     * @return
     * @author h2l
     */
    public Boolean sIsMember(String key, byte[] value) {
        RedisConnection conn = null;
        try {
            conn = jedisConnectionFactory.getConnection();
            return conn.sIsMember((redisKey + key).getBytes(), value);
        } catch (Exception e) {
            log.error(LOG_ERROR_REDIS_CACHE, e);
        } finally {
            if (conn != null) {
                conn.close();
            }
        }
        return false;
    }

    /**
     * Redis Hset 命令用于为哈希表中的字段赋值 。 如果哈希表不存在，一个新的哈希表被创建并进行 HSET 操作。
     * 如果字段已经存在于哈希表中，旧值将被覆盖。
     *
     * @param key   redis 中Map 的key
     * @param field 设置元素的key
     * @param value 元素的 value
     * @return
     * @author h2l
     */
    public Boolean hSet(String key, byte[] field, byte[] value) {
        RedisConnection conn = null;
        try {
            conn = jedisConnectionFactory.getConnection();
            return conn.hSet((redisKey + key).getBytes(), field, value);
        } catch (Exception e) {
            log.error(LOG_ERROR_REDIS_CACHE, e);
        } finally {
            if (conn != null) {
                conn.close();
            }
        }
        return false;
    }

    /**
     * Redis Hsetnx 命令用于为哈希表中不存在的的字段赋值 。 如果哈希表不存在，一个新的哈希表被创建并进行 HSET 操作。
     * 如果字段已经存在于哈希表中，操作无效。 如果 key 不存在，一个新哈希表被创建并执行 HSETNX 命令。
     *
     * @param key   redis 中Map 的key
     * @param field 设置元素的key
     * @param value 元素的 value
     * @return
     * @author h2l
     */
    public Boolean hSetNX(String key, byte[] field, byte[] value) {
        RedisConnection conn = null;
        try {
            conn = jedisConnectionFactory.getConnection();
            return conn.hSetNX((redisKey + key).getBytes(), field, value);
        } catch (Exception e) {
            log.error(LOG_ERROR_REDIS_CACHE, e);
        } finally {
            if (conn != null) {
                conn.close();
            }
        }
        return false;
    }

    /**
     * Redis Hdel 命令用于删除哈希表 key 中的一个或多个指定字段，不存在的字段将被忽略。
     *
     * @param key    redis 中Map 的key
     * @param fields 删除元素的key
     * @return
     * @author h2l
     */
    public Long hDel(String key, byte[]... fields) {
        RedisConnection conn = null;
        try {
            conn = jedisConnectionFactory.getConnection();
            return conn.hDel((redisKey + key).getBytes(), fields);
        } catch (Exception e) {
            log.error(LOG_ERROR_REDIS_CACHE, e);
        } finally {
            if (conn != null) {
                conn.close();
            }
        }
        return null;
    }

    /**
     * Redis Hgetall 命令用于返回哈希表中，所有的字段和值。 在返回值里，紧跟每个字段名(field
     * name)之后是字段的值(value)，所以返回值的长度是哈希表大小的两倍。
     *
     * @param key
     * @return
     * @author h2l
     */
    public Map<byte[], byte[]> hGetAll(String key) {
        RedisConnection conn = null;
        try {
            conn = jedisConnectionFactory.getConnection();
            return conn.hGetAll((redisKey + key).getBytes());
        } catch (Exception e) {
            log.error(LOG_ERROR_REDIS_CACHE, e);
        } finally {
            if (conn != null) {
                conn.close();
            }
        }
        return null;
    }

    /**
     * Redis Hget 命令用于返回哈希表中指定字段的值。
     *
     * @param key
     * @return
     * @author h2l
     */
    public byte[] hGet(String key, byte[] field) {
        RedisConnection conn = null;
        try {
            conn = jedisConnectionFactory.getConnection();
            return conn.hGet((redisKey + key).getBytes(), field);
        } catch (Exception e) {
            log.error(LOG_ERROR_REDIS_CACHE, e);
        } finally {
            if (conn != null) {
                conn.close();
            }
        }
        return null;
    }

    /**
     * Redis Hmget 命令用于返回哈希表中，一个或多个给定字段的值。 如果指定的字段不存在于哈希表，那么返回一个 nil 值。
     *
     * @param key
     * @param fields
     * @return
     * @author h2l
     */
    public List<byte[]> hMGet(String key, byte[]... fields) {
        RedisConnection conn = null;
        try {
            conn = jedisConnectionFactory.getConnection();
            return conn.hMGet((redisKey + key).getBytes(), fields);
        } catch (Exception e) {
            log.error(LOG_ERROR_REDIS_CACHE, e);
        } finally {
            if (conn != null) {
                conn.close();
            }
        }
        return null;
    }

    /**
     * Redis Hvals 命令返回哈希表所有域(field)的值。
     *
     * @param key
     * @return
     * @author h2l
     */
    public List<byte[]> hVals(String key) {
        RedisConnection conn = null;
        try {
            conn = jedisConnectionFactory.getConnection();
            return conn.hVals((redisKey + key).getBytes());
        } catch (Exception e) {
            log.error(LOG_ERROR_REDIS_CACHE, e);
        } finally {
            if (conn != null) {
                conn.close();
            }
        }
        return null;
    }

    /**
     * 将 key 中储存的数字值增一
     *
     * @param key
     * @param expirationTime 过期时间 单位 (秒) ≤ 0 无期限
     * @return
     * @author h2l
     */
    public Long incr(String key, int expirationTime) {
        RedisConnection conn = null;
        try {
            conn = jedisConnectionFactory.getConnection();
            byte[] bytes = (redisKey + key).getBytes();
            Long incr = conn.incr(bytes);
            if (expirationTime > 0) {
                conn.expire(bytes, expirationTime);
            }
            return incr;
        } catch (Exception e) {
            log.error(LOG_ERROR_REDIS_CACHE, e);
        } finally {
            if (conn != null) {
                conn.close();
            }
        }
        return null;
    }

    private JavaType getCollectionType(ObjectMapper mapper, Class<?> collectionClass, Class<?>... elementClasses) {
        return mapper.getTypeFactory().constructParametricType(collectionClass, elementClasses);
    }
}
